﻿using System;
using System.Reflection;
using SlimDX;
using SlimDX.XInput;

namespace GamepadDriver
{
	public class EventWrappedController
	{
		#region Constants

		private const int DEFAULT_SENSITIVITY = 32768;

		#endregion

		#region Events

		public event EventHandler<ButtonEventArgs> DPadUpPressed;
		public event EventHandler<ButtonEventArgs> DPadDownPressed;
		public event EventHandler<ButtonEventArgs> DPadLeftPressed;
		public event EventHandler<ButtonEventArgs> DPadRightPressed;
		public event EventHandler<ButtonEventArgs> APressed;
		public event EventHandler<ButtonEventArgs> BPressed;
		public event EventHandler<ButtonEventArgs> XPressed;
		public event EventHandler<ButtonEventArgs> YPressed;
		public event EventHandler<ButtonEventArgs> StartPressed;
		public event EventHandler<ButtonEventArgs> BackPressed;
		public event EventHandler<ButtonEventArgs> LeftThumbPressed;
		public event EventHandler<ButtonEventArgs> RightThumbPressed;
		public event EventHandler<ButtonEventArgs> LeftShoulderPressed;
		public event EventHandler<ButtonEventArgs> RightShoulderPressed;

		public event EventHandler<ButtonEventArgs> DPadUpReleased;
		public event EventHandler<ButtonEventArgs> DPadDownReleased;
		public event EventHandler<ButtonEventArgs> DPadLeftReleased;
		public event EventHandler<ButtonEventArgs> DPadRightReleased;
		public event EventHandler<ButtonEventArgs> AReleased;
		public event EventHandler<ButtonEventArgs> BReleased;
		public event EventHandler<ButtonEventArgs> XReleased;
		public event EventHandler<ButtonEventArgs> YReleased;
		public event EventHandler<ButtonEventArgs> StartReleased;
		public event EventHandler<ButtonEventArgs> BackReleased;
		public event EventHandler<ButtonEventArgs> LeftThumbReleased;
		public event EventHandler<ButtonEventArgs> RightThumbReleased;
		public event EventHandler<ButtonEventArgs> LeftShoulderReleased;
		public event EventHandler<ButtonEventArgs> RightShoulderReleased;

		public event EventHandler<ThumbEventArgs> LeftThumbUsed;
		public event EventHandler<ThumbEventArgs> RightThumbUsed;
		public event EventHandler<TriggerEventArgs> LeftTriggerUsed;
		public event EventHandler<TriggerEventArgs> RightTriggerUsed;

		#endregion

		#region Variables

		public Controller _controller;
		private Gamepad _previousGamepad;

		private int _leftThumbSensitivity;
		private int _rightThumbSensitivity;
		private int _leftTriggerSensitivity;
		private int _rightTriggerSensitivity;

		private DateTime _leftThumbNext;
		private DateTime _rightThumbNext;
		private DateTime _leftTriggerNext;
		private DateTime _rightTriggerNext;

		#endregion

		#region Constructors

		public EventWrappedController(UserIndex userIndex)
		{
			UserIndex = userIndex;
			_controller = new Controller(UserIndex);

			_leftThumbNext = DateTime.Now;
			_leftTriggerNext = DateTime.Now;
			_rightThumbNext = DateTime.Now;
			_rightTriggerNext = DateTime.Now;

			_leftThumbSensitivity = DEFAULT_SENSITIVITY;
			_rightThumbSensitivity = DEFAULT_SENSITIVITY;
			_leftTriggerSensitivity = DEFAULT_SENSITIVITY;
			_rightTriggerSensitivity = DEFAULT_SENSITIVITY;
		}

		#endregion

		#region Properties

		public UserIndex UserIndex { get; private set; }

		public Gamepad Gamepad
		{
			get
			{
				return _previousGamepad;
			}
		}

		#endregion

		#region Methods

		public void Update()
		{
			var gamepad = _controller.GetState().Gamepad;

			foreach (GamepadButtonFlags button in Enum.GetValues(typeof(GamepadButtonFlags)))
			{
				if (gamepad.IsButtonPressed(button) && !_previousGamepad.IsButtonPressed(button))
				{
					this.FireEvent(GetPressedEvent(button).Name, this, new ButtonEventArgs(button, ButtonState.Pressed));
				}
				else if (!gamepad.IsButtonPressed(button) && _previousGamepad.IsButtonPressed(button))
				{
					this.FireEvent(GetPressedEvent(button).Name, this, new ButtonEventArgs(button, ButtonState.Released));
				}
			}

			if (DateTime.Now >= _leftThumbNext)
			{
				var addTicks = _leftThumbSensitivity - _leftThumbSensitivity * 0.9 * Math.Abs(gamepad.GetLeftThumb().Length());
				this.FireEvent("LeftThumbUsed", this, new ThumbEventArgs(GamepadThumb.Left, gamepad.GetLeftThumb().InvertTheYAxis()));
				_leftThumbNext = _leftThumbNext.AddTicks((long)addTicks);
			}

			if (DateTime.Now >= _rightThumbNext)
			{
				var addTicks = _rightThumbSensitivity - _rightThumbSensitivity * 0.9 * Math.Abs(gamepad.GetRightThumb().Length());
				this.FireEvent("RightThumbUsed", this, new ThumbEventArgs(GamepadThumb.Right, gamepad.GetRightThumb().InvertTheYAxis()));
				_rightThumbNext = _rightThumbNext.AddTicks((long)addTicks);
			}

			if (DateTime.Now >= _leftTriggerNext)
			{
				var addTicks = _leftTriggerSensitivity - _leftTriggerSensitivity * 0.9 * Math.Abs(gamepad.LeftTrigger * (65536.0 / 255.0) - 32768.0);
				this.FireEvent("LeftTriggerUsed", this, new TriggerEventArgs(GamepadTrigger.Left, gamepad.LeftTrigger));
				_leftTriggerNext = _leftTriggerNext.AddTicks((long)addTicks);
			}

			if (DateTime.Now >= _rightTriggerNext)
			{
				var addTicks = _rightTriggerSensitivity - _rightTriggerSensitivity * 0.9 * Math.Abs(gamepad.RightTrigger * (65536.0 / 255.0) * 32768.0);
				this.FireEvent("RightTriggerUsed", this, new TriggerEventArgs(GamepadTrigger.Right, gamepad.RightTrigger));
				_rightTriggerNext = _rightTriggerNext.AddTicks((long)addTicks);
			}

			_previousGamepad = gamepad;
		}

		public void Map(GamepadButtonFlags button, Action<ButtonState> action)
		{
			GetPressedEvent(button).AddEventHandler(this, (EventHandler<ButtonEventArgs>)((sender, e) => action(e.State)));
			GetReleasedEvent(button).AddEventHandler(this, (EventHandler<ButtonEventArgs>)((sender, e) => action(e.State)));
		}

		public void Map(GamepadThumb button, Action<Vector2> action)
		{
			switch (button)
			{
				case GamepadThumb.Left:
					LeftThumbUsed += (sender, e) => action(e.Amount);
					break;
				case GamepadThumb.Right:
					RightThumbUsed += (sender, e) => action(e.Amount);
					break;
			}
		}

		public void Map(GamepadTrigger button, Action<byte> action)
		{
			switch (button)
			{
				case GamepadTrigger.Left:
					LeftTriggerUsed += (sender, e) => action(e.Amount);
					break;
				case GamepadTrigger.Right:
					RightTriggerUsed += (sender, e) => action(e.Amount);
					break;
			}
		}

		public void SetSensitivity(GamepadThumb button, int sensitivity)
		{
			switch (button)
			{
				case GamepadThumb.Left:
					_leftThumbSensitivity = sensitivity;
					break;
				case GamepadThumb.Right:
					_rightThumbSensitivity = sensitivity;
					break;
			}
		}

		public void SetSensitivity(GamepadTrigger button, int sensitivity)
		{
			switch (button)
			{
				case GamepadTrigger.Left:
					_leftTriggerSensitivity = sensitivity;
					break;
				case GamepadTrigger.Right:
					_rightTriggerSensitivity = sensitivity;
					break;
			}
		}

		private EventInfo GetPressedEvent(GamepadButtonFlags button)
		{
			return GetType().GetEvent(button.ToString() + "Pressed");
		}

		private EventInfo GetReleasedEvent(GamepadButtonFlags button)
		{
			return GetType().GetEvent(button.ToString() + "Released");
		}

		#endregion
	}
}